package com.wosai.register.service;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;


public class RegisterExceptionResolver extends AbstractHandlerExceptionResolver {

    private HttpMessageConverter<?>[] messageConverters =
            new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
                    new MappingJackson2HttpMessageConverter()};

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request,
                                              HttpServletResponse response,
                                              Object handler, Exception ex) {

        try {
            HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
            List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
            if (acceptedMediaTypes.isEmpty()) {
                acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
            }
            MediaType.sortByQualityValue(acceptedMediaTypes);
            HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);

            Object returnValue = handleFrameworkException(ex, response);
            if (returnValue==null && ex instanceof RuntimeException)
                returnValue = handleRuntimeException((RuntimeException)ex, response);

            if (returnValue == null)
                return null;

            Class<?> returnValueType = returnValue.getClass();
            if (this.messageConverters != null) {
                for (MediaType acceptedMediaType : acceptedMediaTypes) {
                    for (HttpMessageConverter messageConverter : this.messageConverters) {
                        if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
                            messageConverter.write(returnValue, acceptedMediaType, outputMessage);
                            return new ModelAndView();
                        }
                    }
                }
            }
            if (logger.isWarnEnabled()) {
                logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " +
                        acceptedMediaTypes);
            }
        }catch(Exception e){
            logger.warn("Handling of exception resulted in exception " + ex, e);
        }
        return null;
        
    }
    
    private Map<String, Object> handleRuntimeException(RuntimeException ex, HttpServletResponse response) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("code", 5001);
        result.put("message", ex.getMessage());
        
        logger.error("Leaking runtime exception", ex);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return result;
    }

    private Map<String, Object> handleFrameworkException(Exception ex, HttpServletResponse response) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("code", 6001);
        result.put("message", ex.getMessage());
        
        
        if (ex instanceof HttpClientErrorException){
            response.setStatus(((HttpClientErrorException)ex).getStatusCode().value());
        }
        else if (ex instanceof NoSuchRequestHandlingMethodException) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
        else if (ex instanceof HttpRequestMethodNotSupportedException) {
            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        }
        else if (ex instanceof HttpMediaTypeNotSupportedException) {
            response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
        }
        else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
        }
        else if (ex instanceof MissingServletRequestParameterException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
        else if (ex instanceof ServletRequestBindingException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
        else if (ex instanceof ConversionNotSupportedException) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        else if (ex instanceof TypeMismatchException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
        else if (ex instanceof HttpMessageNotReadableException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
        else if (ex instanceof HttpMessageNotWritableException) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        else if (ex instanceof MethodArgumentNotValidException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
        else if (ex instanceof MissingServletRequestPartException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }else{
            return null;
        }

        logger.error("Framework exception:", ex);
        
        return result;
        
    }

}
